#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Threading;
using System.Threading.Tasks;
using BitwiseOre.SharedMemory.Utils;
using NUnit.Framework;
using Moq;

namespace BitwiseOre.Tests.SharedMemory.Utils
{
    [TestFixture]
    public sealed class AsyncResultEnderFixture : AssertionHelper
    {
        [Test(Description = "Ensures that attempting to end the null task throws an exception")]
        public void EndTask_WithNullAsyncResult()
        {
            var ender = new AsyncResultEnder<TimeSpan>(new TaskEnder<Task<TimeSpan>>());

            Expect(() => ender.EndTask(null, ae => null), Throws.InstanceOf<ArgumentNullException>(),
                   "Attempting to end a null task did not throw the expected exception.");
        }

        [Test(Description = "Ensures that attempting to end a task that is not of the expected type throws an excpetion.")]
        public void EndTask_WithForeignAsyncResult()
        {
            var ender = new AsyncResultEnder<TimeSpan>(new TaskEnder<Task<TimeSpan>>());

            Expect(() => ender.EndTask(new Mock<IAsyncResult>().Object, ae => null), Throws.ArgumentException,
                "Attepting to end a foreign task did not throw the expected excception.");
        }

        [Test(Description = "Checks that ending a task waits for the task to complete")]
        public void EndTask_WaitsForTaskCompletions()
        {
            var source = new TaskCompletionSource<TimeSpan>();
            var ender = new AsyncResultEnder<TimeSpan>(new TaskEnder<Task<TimeSpan>>());
            var starter = new ManualResetEvent(false);
            var stopper = new ManualResetEvent(false);

            var task = Task.Factory.StartNew(() =>
                                                 {
                                                     starter.Set();
                                                     ender.EndTask(source.Task, ae => null);
                                                     stopper.Set();
                                                 });

            starter.WaitOne();
            var ended = stopper.WaitOne(TimeSpan.FromSeconds(1));

            Expect(task.Status, EqualTo(TaskStatus.Running),
                   "Ending an unfinished task did not wait for the task to complete.");
            Expect(!ended, "Ending an unfinished tasked did not wait for the task to complete.");
        }

        [Test(Description = "Checks that ending a timed out task throws a timeout exception.")]
        public void EndTask_WithTimedOutTask()
        {
            var ender = new AsyncResultEnder<TimeSpan>(new TaskEnder<Task<TimeSpan>>());
            var source = new TaskCompletionSource<TimeSpan>();

            source.SetException(new TimeoutException());
            Expect(() => ender.EndTask(source.Task, ae => null), Throws.InstanceOf<TimeoutException>(),
                "Ending a timed out task did not throw a timeout exception.");
        }

        [Test(Description = "Checks that ending a task in a faulted state uses the exception factory.")]
        public void EndTask_WithExceptionalTask()
        {
            var source = new TaskCompletionSource<TimeSpan>();
            source.SetException(new InvalidOperationException());
            var factoryused = false;
            Func<AggregateException, Exception> factory = ae =>
                                                              {
                                                                  factoryused = true;
                                                                  return new Exception();
                                                              };
            var ender = new AsyncResultEnder<TimeSpan>(new TaskEnder<Task<TimeSpan>>());

            try
            {
                ender.EndTask(source.Task, factory);
            }
            catch { }

            Expect(factoryused, "Exception factory not invoked.");
        }

        [Test(Description = "Ensures that ending a task in a faulted state throws the exception returned by the factory")]
        public void EndTask_WithExceptionTaskThrowsCreatedException()
        {
            var source = new TaskCompletionSource<TimeSpan>();
            source.SetException(new InvalidOperationException());
            var exception = new Exception();
            Func<AggregateException, Exception> factory = ae => exception;
            var ender = new AsyncResultEnder<TimeSpan>(new TaskEnder<Task<TimeSpan>>());

            Expect(() => ender.EndTask(source.Task, factory), Throws.Exception.EqualTo(exception),
                   "Unexpected exception thrown when ending a faulted task.");
        }

        [Test(Description = "Checks that ending a task returns the result of that task.")]
        public void EndTask_ReturnsTaskResult()
        {
            var expectedresult = TimeSpan.FromMinutes(5);
            var task = Task.Factory.StartNew(() => expectedresult);
            var ender = new AsyncResultEnder<TimeSpan>(new TaskEnder<Task<TimeSpan>>());

            var result = ender.EndTask(task, ae => null);

            Expect(result, EqualTo(expectedresult), "Unexpected result returned from ending a task.");
        }

    }

}